package com.telit.common.util;

import cn.hutool.core.map.MapUtil;
import com.telit.common.constant.Constant;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpHeaders;

import javax.net.ssl.*;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate;
import java.util.*;


/**
 * Http工具类
 *
 * @author yuezhuang
 * @date 2022-10-28
 */
public class WebUtils {

    /**
     * 覆盖java默认的证书验证
     */
    private static final TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
        public void checkClientTrusted(X509Certificate[] chain, String authType) {
        }

        public void checkServerTrusted(X509Certificate[] chain, String authType) {
        }

        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[]{};
        }
    }};

    public static Map<String, Object> getRequestParameters(HttpServletRequest httpServletRequest) {
        Map<String, String[]> parameterMap = httpServletRequest.getParameterMap();
        Map<String, Object> requestParameters = new LinkedHashMap<>();
        for (Map.Entry<String, String[]> requestParameter : parameterMap.entrySet()) {
            requestParameters.put(requestParameter.getKey(), StringUtils.trimToNull(
                    org.springframework.util.StringUtils.arrayToCommaDelimitedString(requestParameter.getValue())));
        }
        return requestParameters;
    }

    public static String doPostWithRequestParameters(boolean isEncode, String requestUrl,
                                                     Map<String, String> parameters, Map<String, String> headers,
                                                     String charset, int connectTimeout, int readTimeout)
            throws IOException {
        return doPostWithRequestParameters(false, isEncode, requestUrl, null, parameters, headers, connectTimeout,
                readTimeout, charset);
    }

    public static String doPostWithRequestParameters(boolean isEncode, String requestUrl,
                                                     Map<String, String> parameters, Map<String, String> headers,
                                                     String charset)
            throws IOException {
        return doPostWithRequestParameters(false, isEncode, requestUrl, null, parameters, headers, 0, 0, charset);
    }

    public static String doPostWithRequestParameters(boolean ignoreCert, boolean isEncode, String requestUrl,
                                                     String queryString,
                                                     Map<String, String> parameters, Map<String, String> headers,
                                                     int connectTimeout, int readTimeout, String charset)
            throws IOException {
        if (StringUtils.isBlank(charset)) {
            charset = Constant.CHARSET_NAME_UTF_8;
        }
        String requestBody = constructRequestBody(parameters, isEncode, charset);
        return doPost(ignoreCert, requestUrl, queryString, requestBody, headers, connectTimeout, readTimeout, charset);
    }

    public static String constructRequestBody(Map<String, String> parameters, boolean isEncode, String charset) {
        StringBuilder requestBody = new StringBuilder();
        try {
            for (Map.Entry<String, String> parameter : parameters.entrySet()) {
                requestBody.append("&").append(parameter.getKey()).append("=");
                String value = parameter.getValue() == null ? "" : parameter.getValue();
                if (isEncode) {
                    requestBody.append(URLEncoder.encode(value, charset));
                } else {
                    requestBody.append(value);
                }
            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return requestBody.deleteCharAt(0).toString();
    }

    private static String doPost(boolean ignoreCert, String requestUrl, String queryString, String requestBody,
                                 Map<String, String> headers, int connectTimeout, int readTimeout, String charset)
            throws IOException {
        if (StringUtils.isNotBlank(queryString)) {
            requestUrl = requestUrl + "?" + queryString;
        }

        HttpURLConnection httpURLConnection = null;
        String result = null;
        PrintWriter printWriter = null;
        OutputStreamWriter outputStreamWriter = null;

        try {
            httpURLConnection = buildHttpURLConnection(ignoreCert, requestUrl, Constant.REQUEST_METHOD_POST,
                    obtainHeaders(headers),
                    connectTimeout, readTimeout);
            httpURLConnection.setDoInput(true);
            httpURLConnection.setDoOutput(true);

            httpURLConnection.connect();
            outputStreamWriter = new OutputStreamWriter(httpURLConnection.getOutputStream(), charset);
            printWriter = new PrintWriter(outputStreamWriter);
            printWriter.write(requestBody);
            printWriter.flush();

//            result = inputStreamToString(httpURLConnection.getInputStream());

            result = obtainResult(httpURLConnection, httpURLConnection.getResponseCode(), charset);
        } catch (IOException e) {
            throw e;
        } finally {
            if (printWriter != null) {
                printWriter.close();
            }
            if (outputStreamWriter != null) {
                outputStreamWriter.close();
            }
            if (httpURLConnection != null) {
                httpURLConnection.disconnect();
            }
        }
        return result;
    }

    public static HttpURLConnection buildHttpURLConnection(boolean ignoreCert, String requestUrl, String requestMethod,
                                                           Map<String, String> headers, int connectTimeout,
                                                           int readTimeout) throws IOException {
        URL url = new URL(requestUrl);
        URLConnection urlConnection = url.openConnection();
        if (url.getProtocol().equalsIgnoreCase("https") && ignoreCert) {
            trustAllHosts((HttpsURLConnection) urlConnection);
        }
        HttpURLConnection httpURLConnection = (HttpURLConnection) urlConnection;
        httpURLConnection.setRequestMethod(requestMethod);
        if (MapUtil.isNotEmpty(headers)) {
            for (Map.Entry<String, String> header : headers.entrySet()) {
                httpURLConnection.setRequestProperty(header.getKey(), header.getValue());
            }
        }
        httpURLConnection.setConnectTimeout(connectTimeout);
        httpURLConnection.setReadTimeout(readTimeout);
        httpURLConnection.setUseCaches(false);
        return httpURLConnection;
    }

    private static Map<String, String> obtainHeaders(Map<String, String> headers) {
        Map<String, String> requestHeaders = null;
        if (MapUtil.isEmpty(headers)) {
            requestHeaders = new HashMap<String, String>();
        } else {
            requestHeaders = new HashMap<String, String>(headers);
        }
        if (!requestHeaders.containsKey("Content-Type")) {
            requestHeaders.put("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
        }

        if (!requestHeaders.containsKey("Accept")) {
            requestHeaders.put("Accept", "text/xml,text/javascript,application/json");
        }
        return requestHeaders;
    }

    public static String obtainResult(HttpURLConnection httpURLConnection, int responseCode, String charsetName)
            throws IOException {
        String result = null;
        if (responseCode == HttpURLConnection.HTTP_OK) {
            result = inputStreamToString(httpURLConnection.getInputStream(),
                    obtainResponseCharset(httpURLConnection, charsetName));
            httpURLConnection.disconnect();
        } else {
            result = inputStreamToString(httpURLConnection.getErrorStream(),
                    obtainResponseCharset(httpURLConnection, charsetName));
            httpURLConnection.disconnect();
        }
        return result;
    }

    /**
     * 信任所有
     */
    private static SSLSocketFactory trustAllHosts(HttpsURLConnection connection) {
        SSLSocketFactory oldFactory = connection.getSSLSocketFactory();
        try {
            HostnameVerifier hv = (s, sslSession) -> true;
            SSLContext sc = SSLContext.getInstance("TLS");
            sc.init(null, trustAllCerts, new java.security.SecureRandom());
            SSLSocketFactory newFactory = sc.getSocketFactory();
            connection.setSSLSocketFactory(newFactory);
            connection.setHostnameVerifier(hv);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return oldFactory;
    }

    public static String inputStreamToString(InputStream inputStream, String charsetName) throws IOException {
        StringBuffer result = new StringBuffer();
        InputStreamReader inputStreamReader = new InputStreamReader(inputStream, charsetName);
        int length = 0;
        char[] buffer = new char[1024];
        while ((length = inputStreamReader.read(buffer, 0, 1024)) != -1) {
            result.append(buffer, 0, length);
        }
        inputStreamReader.close();
        return result.toString();
    }

    public static String obtainResponseCharset(HttpURLConnection httpURLConnection, String defaultCharset) {
        String charset = null;
        String contentType = httpURLConnection.getContentType();
        if (StringUtils.isNotBlank(contentType)) {
            contentType = contentType.toUpperCase();
            String[] array = contentType.split(";");
            int length = array.length;
            for (int index = 0; index < length; index++) {
                String str = array[index].trim();
                if (str.startsWith("CHARSET")) {
                    String[] pair = str.split("=");
                    charset = pair[1].trim();
                    break;
                }
            }
        }
        if (StringUtils.isBlank(charset)) {
            charset = defaultCharset;
        }
        return charset;
    }

    public static String doPostWithRequestParameters(boolean isEncode, String requestUrl,
                                                     Map<String, String> parameters, Map<String, String> headers)
            throws IOException {
        return doPostWithRequestParameters(false, isEncode, requestUrl, null, parameters, headers, 0, 0);
    }

    public static String doPostWithRequestParameters(boolean ignoreCert, boolean isEncode, String requestUrl,
                                                     String queryString,
                                                     Map<String, String> parameters, Map<String, String> headers,
                                                     int connectTimeout, int readTimeout) throws IOException {
        String requestBody = constructRequestBody(parameters, isEncode);
        return doPost(ignoreCert, requestUrl, queryString, requestBody, headers, connectTimeout, readTimeout);
    }

    public static String constructRequestBody(Map<String, String> parameters, boolean isEncode) {
        return constructRequestBody(parameters, isEncode, "UTF-8");
    }

    private static String doPost(boolean ignoreCert, String requestUrl, String queryString, String requestBody,
                                 Map<String, String> headers, int connectTimeout, int readTimeout) throws IOException {
        return doPost(ignoreCert, requestUrl, queryString, requestBody, headers, connectTimeout, readTimeout,
                Constant.CHARSET_NAME_UTF_8);
    }

    public static String doPostWithRequestParameters(String requestUrl, Map<String, String> parameters,
                                                     Map<String, String> headers) throws IOException {
        return doPostWithRequestParameters(false, false, requestUrl, null, parameters, headers, 0, 0);
    }

    public static String doPostWithRequestParameters(String requestUrl, Map<String, String> parameters,
                                                     int connectTimeout, int readTimeout) throws IOException {
        return doPostWithRequestParameters(false, false, requestUrl, null, parameters, null, connectTimeout,
                readTimeout);
    }

    public static String doPostWithRequestParameters(String requestUrl, Map<String, String> parameters,
                                                     Map<String, String> headers, int connectTimeout, int readTimeout)
            throws IOException {
        return doPostWithRequestParameters(false, requestUrl, null, parameters, headers, connectTimeout, readTimeout);
    }

    public static String doPostWithRequestParameters(boolean ignoreCert, String requestUrl, String queryString,
                                                     Map<String, String> parameters, Map<String, String> headers,
                                                     int connectTimeout, int readTimeout)
            throws IOException {
        return doPostWithRequestParameters(ignoreCert, false, requestUrl, queryString, parameters, headers,
                connectTimeout, readTimeout);
    }

    public static String doPostWithRequestParameters(String requestUrl, String queryString,
                                                     Map<String, String> parameters, int connectTimeout,
                                                     int readTimeout) throws IOException {
        return doPostWithRequestParameters(false, false, requestUrl, queryString, parameters, null, connectTimeout,
                readTimeout);
    }

    public static String constructRequestBody(Map<String, String> parameters) {
        return constructRequestBody(parameters, false);
    }

    public static String doPostWithRequestBody(String requestUrl, String requestBody, int connectTimeout,
                                               int readTimeout) throws IOException {
        return doPost(false, requestUrl, null, requestBody, null, connectTimeout, readTimeout);
    }

    public static String doPostWithRequestBody(String requestUrl, String queryString, String requestBody,
                                               int connectTimeout, int readTimeout) throws IOException {
        return doPost(false, requestUrl, queryString, requestBody, null, connectTimeout, readTimeout);
    }

    public static String doPostWithRequestBody(boolean ignoreCert, String requestUrl, String requestBody,
                                               Map<String, String> headers, int connectTimeout, int readTimeout)
            throws IOException {
        return doPost(ignoreCert, requestUrl, null, requestBody, headers, connectTimeout, readTimeout);
    }

    public static String doPostWithRequestBody(String requestUrl, String requestBody, Map<String, String> headers,
                                               int connectTimeout, int readTimeout) throws IOException {
        return doPost(false, requestUrl, null, requestBody, headers, connectTimeout, readTimeout);
    }

    public static String doPostWithRequestBody(String requestUrl, String requestBody, Map<String, String> headers,
                                               int connectTimeout, int readTimeout, String charset) throws IOException {
        return doPost(false, requestUrl, null, requestBody, headers, connectTimeout, readTimeout, charset);
    }

    public static String doPostWithRequestBody(String requestUrl, String queryString, String requestBody,
                                               Map<String, String> headers, int connectTimeout, int readTimeout)
            throws IOException {
        return doPost(false, requestUrl, queryString, requestBody, headers, connectTimeout, readTimeout);
    }

    public static HttpURLConnection buildHttpURLConnection(String requestUrl, String requestMethod,
                                                           Map<String, String> headers, int connectTimeout,
                                                           int readTimeout) throws IOException {
        return buildHttpURLConnection(false, requestUrl, requestMethod, headers, connectTimeout, readTimeout);
    }

    public static String inputStreamToString(InputStream inputStream) throws IOException {
        InputStreamReader inputStreamReader = null;
        BufferedReader bufferedReader = null;
        StringBuffer stringBuffer = null;
        try {
            inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
            bufferedReader = new BufferedReader(inputStreamReader);
            stringBuffer = new StringBuffer();
            String line = null;
            while ((line = bufferedReader.readLine()) != null) {
                stringBuffer.append(line);
            }
        } catch (IOException e) {
            throw e;
        } finally {
            if (bufferedReader != null) {
                bufferedReader.close();
            }
            if (inputStreamReader != null) {
                inputStreamReader.close();
            }
        }
        return stringBuffer.toString();
    }

    public static String doGetWithRequestParameters(String requestUrl, Map<String, String> requestParameters)
            throws IOException {
        return doGetWithRequestParameters(requestUrl, null, requestParameters);
    }

    public static String doGetWithRequestParameters(String requestUrl, Map<String, String> requestParameters,
                                                    String charsetName) throws IOException {
        return doGetWithRequestParameters(requestUrl, null, requestParameters, charsetName);
    }

    public static String doGetWithRequestParameters(String requestUrl, int readTimeout, int connectTimeout,
                                                    Map<String, String> requestParameters) throws IOException {
        return doGetWithRequestParameters(requestUrl, readTimeout, connectTimeout, null, requestParameters,
                Constant.CHARSET_NAME_UTF_8);
    }

    public static String doGetWithRequestParameters(String requestUrl, int readTimeout, int connectTimeout,
                                                    Map<String, String> requestParameters, String charsetName)
            throws IOException {
        return doGetWithRequestParameters(requestUrl, readTimeout, connectTimeout, null, requestParameters,
                charsetName);
    }

    public static String doGetWithRequestParameters(String requestUrl, Map<String, String> headers,
                                                    Map<String, String> requestParameters) throws IOException {
        return doGetWithRequestParameters(requestUrl, 0, 0, headers, requestParameters, Constant.CHARSET_NAME_UTF_8);
    }

    public static String doGetWithRequestParameters(String requestUrl, Map<String, String> headers,
                                                    Map<String, String> requestParameters, String charsetName)
            throws IOException {
        return doGetWithRequestParameters(requestUrl, 0, 0, headers, requestParameters, charsetName);
    }

    public static String doGetWithRequestParameters(String requestUrl, int readTimeout, int connectTimeout,
                                                    Map<String, String> headers, Map<String, String> requestParameters,
                                                    String charsetName) throws IOException {
        String result = null;
        HttpURLConnection httpURLConnection = null;
        try {
            if (MapUtil.isNotEmpty(requestParameters)) {
                requestUrl = requestUrl + "?" + buildQueryString(requestParameters, charsetName);
            }
            httpURLConnection = buildHttpURLConnection(requestUrl, Constant.REQUEST_METHOD_GET, obtainHeaders(headers),
                    connectTimeout, readTimeout);
            int responseCode = httpURLConnection.getResponseCode();
            if (responseCode == HttpURLConnection.HTTP_MOVED_PERM
                    || responseCode == HttpURLConnection.HTTP_MOVED_TEMP) {
                httpURLConnection.disconnect();
                return doGetWithRequestParameters(httpURLConnection.getHeaderField(HttpHeaders.LOCATION), readTimeout,
                        connectTimeout, headers, null, charsetName);
            } else {
                result = obtainResult(httpURLConnection, responseCode, charsetName);
            }
        } catch (Exception e) {
            if (httpURLConnection != null) {
                httpURLConnection.disconnect();
            }
            throw e;
        }
        return result;
    }

    public static String buildQueryString(Map<String, String> requestParameters) throws UnsupportedEncodingException {
        return buildQueryString(requestParameters, Constant.CHARSET_NAME_UTF_8);
    }

    public static String buildQueryString(Map<String, String> requestParameters, String charsetName)
            throws UnsupportedEncodingException {
        return concat(requestParameters, charsetName);
    }

    public static String concat(Map<String, String> requestParameters, String charsetName)
            throws UnsupportedEncodingException {
        List<String> requestParameterPairs = new ArrayList<String>();
        Set<Map.Entry<String, String>> entries = requestParameters.entrySet();
        for (Map.Entry<String, String> entry : entries) {
            String key = entry.getKey();
            String value = entry.getValue();
            if (StringUtils.isBlank(value)) {
                requestParameterPairs.add(key + "=" + value);
            } else {
                requestParameterPairs.add(key + "=" + URLEncoder.encode(value, charsetName));
            }
        }
        return StringUtils.join(requestParameterPairs, "&");
    }

    public static String buildRequestBody(Map<String, String> requestParameters) throws UnsupportedEncodingException {
        return concat(requestParameters, Constant.CHARSET_NAME_UTF_8);
    }

    public static String buildRequestBody(Map<String, String> requestParameters, String charsetName)
            throws UnsupportedEncodingException {
        return concat(requestParameters, charsetName);
    }

}
